/*
 * Copyright (C) 2017
 *
 * Brick Yang <printfxxx@163.com>
 *
 * This program is free software. You can redistribute it and/or
 * modify it as you like.
 */

/**
 * @file	dpmac.c
 * @brief	dpmac driver
 */

#include <mtrace.h>

#include "dpmac.h"

/* DPMAC Version */
#define DPMAC_CMD_BASE_VERSION	1
#define DPMAC_CMD_2ND_VERSION	2
#define DPMAC_CMD_ID_OFFSET	4

#define DPMAC_CMD(id)		(((id) << DPMAC_CMD_ID_OFFSET) | DPMAC_CMD_BASE_VERSION)
#define DPMAC_CMD_V2(id)	(((id) << DPMAC_CMD_ID_OFFSET) | DPMAC_CMD_2ND_VERSION)

/* Command IDs */
#define DPMAC_CMDID_CLOSE		DPMAC_CMD(0x800)
#define DPMAC_CMDID_OPEN		DPMAC_CMD(0x80c)
#define DPMAC_CMDID_GET_ATTR		DPMAC_CMD(0x004)
#define DPMAC_CMDID_SET_IRQ_ENABLE	DPMAC_CMD(0x012)
#define DPMAC_CMDID_GET_IRQ_ENABLE	DPMAC_CMD(0x013)
#define DPMAC_CMDID_GET_LINK_CFG	DPMAC_CMD(0x0c2)
#define DPMAC_CMDID_GET_LINK_CFG_V2	DPMAC_CMD_V2(0x0c2)
#define DPMAC_CMDID_SET_LINK_STATE	DPMAC_CMD(0x0c3)
#define DPMAC_CMDID_SET_LINK_STATE_V2	DPMAC_CMD_V2(0x0c3)
#define DPMAC_CMDID_GET_COUNTER		DPMAC_CMD(0x0c4)

#define DPMAC_MASK(field)			\
	GENMASK(DPMAC_##field##_SHIFT + DPMAC_##field##_SIZE - 1, \
		DPMAC_##field##_SHIFT)
#define dpmac_set_field(var, field, val)	\
	((var) |= (((val) << DPMAC_##field##_SHIFT) & DPMAC_MASK(field)))
#define dpmac_get_field(var, field)		\
	(((var) & DPMAC_MASK(field)) >> DPMAC_##field##_SHIFT)

struct dpmac_cmd_open {
	u32 dpmac_id;
};

struct dpmac_rsp_get_attributes {
	u8 eth_if;
	u8 link_type;
	u16 id;
	u32 max_rate;
};

struct dpmac_rsp_get_link_cfg {
	u64 options;
	u32 rate;
	u64 advertising;
};

#define DPMAC_STATE_SIZE	1
#define DPMAC_STATE_SHIFT	0
#define DPMAC_STATE_VALID_SIZE	1
#define DPMAC_STATE_VALID_SHIFT	1

struct dpmac_cmd_set_link_state {
	u64 options;
	u32 rate;
	u32 pad;
	u8 up;
	u8 pad0[7];
	u64 supported;
	u64 advertising;
};

struct dpmac_cmd_get_counter {
	u8 type;
};

struct dpmac_rsp_get_counter {
	u64 pad;
	u64 counter;
};

struct dpmac_cmd_set_irq_enable {
	u8 enable;
	u8 pad[3];
	u8 irq_index;
};

struct dpmac_cmd_get_irq_enable {
	u32 pad;
	u8 irq_index;
};

struct dpmac_rsp_get_irq_enable {
	u8 enabled;
};

int dpmac_open(struct fsl_mc_io *mc_io,
	       u32 cmd_flags,
	       int dpmac_id,
	       u16 *token)
{
	struct dpmac_cmd_open *dpmac_cmd;
	struct fsl_mc_command cmd = { 0 };
	int err;

	/* prepare command */
	cmd.header = mc_encode_cmd_header(DPMAC_CMDID_OPEN,
					  cmd_flags,
					  0);
	dpmac_cmd = (struct dpmac_cmd_open *)cmd.params;
	dpmac_cmd->dpmac_id = cpu_to_le32(dpmac_id);

	/* send command to mc */
	err = mc_send_command(mc_io, &cmd);
	if (err)
		return err;

	/* retrieve response parameters */
	*token = mc_cmd_hdr_read_token(&cmd);

	return err;
}

int dpmac_close(struct fsl_mc_io *mc_io,
		u32 cmd_flags,
		u16 token)
{
	struct fsl_mc_command cmd = { 0 };

	/* prepare command */
	cmd.header = mc_encode_cmd_header(DPMAC_CMDID_CLOSE, cmd_flags,
					  token);

	/* send command to mc */
	return mc_send_command(mc_io, &cmd);
}

int dpmac_get_attributes(struct fsl_mc_io *mc_io,
			 u32 cmd_flags,
			 u16 token,
			 struct dpmac_attr *attr)
{
	struct dpmac_rsp_get_attributes *dpmac_rsp;
	struct fsl_mc_command cmd = { 0 };
	int err;

	/* prepare command */
	cmd.header = mc_encode_cmd_header(DPMAC_CMDID_GET_ATTR,
					  cmd_flags,
					  token);

	/* send command to mc */
	err = mc_send_command(mc_io, &cmd);
	if (err)
		return err;

	/* retrieve response parameters */
	dpmac_rsp = (struct dpmac_rsp_get_attributes *)cmd.params;
	attr->eth_if = dpmac_rsp->eth_if;
	attr->link_type = dpmac_rsp->link_type;
	attr->id = le16_to_cpu(dpmac_rsp->id);
	attr->max_rate = le32_to_cpu(dpmac_rsp->max_rate);

	return 0;
}

int dpmac_get_link_cfg(struct fsl_mc_io *mc_io,
		       u32 cmd_flags,
		       u16 token,
		       struct dpmac_link_cfg *cfg)
{
	struct dpmac_rsp_get_link_cfg *dpmac_rsp;
	struct fsl_mc_command cmd = { 0 };
	int err = 0;

	/* prepare command */
	cmd.header = mc_encode_cmd_header(DPMAC_CMDID_GET_LINK_CFG,
					  cmd_flags,
					  token);

	/* send command to mc */
	err = mc_send_command(mc_io, &cmd);
	if (err)
		return err;

	/* retrieve response parameters */
	dpmac_rsp = (struct dpmac_rsp_get_link_cfg *)cmd.params;
	cfg->options = le64_to_cpu(dpmac_rsp->options);
	cfg->rate = le32_to_cpu(dpmac_rsp->rate);

	return 0;
}

int dpmac_get_link_cfg_v2(struct fsl_mc_io *mc_io,
			  u32 cmd_flags,
			  u16 token,
			  struct dpmac_link_cfg *cfg)
{
	struct dpmac_rsp_get_link_cfg *dpmac_rsp;
	struct fsl_mc_command cmd = { 0 };
	int err = 0;

	/* prepare command */
	cmd.header = mc_encode_cmd_header(DPMAC_CMDID_GET_LINK_CFG_V2,
					  cmd_flags,
					  token);

	/* send command to mc */
	err = mc_send_command(mc_io, &cmd);
	if (err)
		return err;

	/* retrieve response parameters */
	dpmac_rsp = (struct dpmac_rsp_get_link_cfg *)cmd.params;
	cfg->options = le64_to_cpu(dpmac_rsp->options);
	cfg->rate = le32_to_cpu(dpmac_rsp->rate);
	cfg->advertising = le64_to_cpu(dpmac_rsp->advertising);

	return 0;
}

int dpmac_set_link_state(struct fsl_mc_io *mc_io,
			 u32 cmd_flags,
			 u16 token,
			 struct dpmac_link_state *link_state)
{
	struct dpmac_cmd_set_link_state *dpmac_cmd;
	struct fsl_mc_command cmd = { 0 };

	/* prepare command */
	cmd.header = mc_encode_cmd_header(DPMAC_CMDID_SET_LINK_STATE,
					  cmd_flags,
					  token);
	dpmac_cmd = (struct dpmac_cmd_set_link_state *)cmd.params;
	dpmac_cmd->options = cpu_to_le64(link_state->options);
	dpmac_cmd->rate = cpu_to_le32(link_state->rate);
	dpmac_cmd->up = dpmac_get_field(link_state->up, STATE);

	/* send command to mc*/
	return mc_send_command(mc_io, &cmd);
}

int dpmac_set_link_state_v2(struct fsl_mc_io *mc_io,
			    u32 cmd_flags,
			    u16 token,
			    struct dpmac_link_state *link_state)
{
	struct dpmac_cmd_set_link_state *dpmac_cmd;
	struct fsl_mc_command cmd = { 0 };

	/* prepare command */
	cmd.header = mc_encode_cmd_header(DPMAC_CMDID_SET_LINK_STATE_V2,
					  cmd_flags,
					  token);
	dpmac_cmd = (struct dpmac_cmd_set_link_state *)cmd.params;
	dpmac_cmd->options = cpu_to_le64(link_state->options);
	dpmac_cmd->rate = cpu_to_le32(link_state->rate);
	dpmac_set_field(dpmac_cmd->up, STATE, link_state->up);
	dpmac_set_field(dpmac_cmd->up, STATE_VALID, link_state->state_valid);
	dpmac_cmd->supported = cpu_to_le64(link_state->supported);
	dpmac_cmd->advertising = cpu_to_le64(link_state->advertising);

	/* send command to mc*/
	return mc_send_command(mc_io, &cmd);
}

int dpmac_get_counter(struct fsl_mc_io *mc_io,
		      u32 cmd_flags,
		      u16 token,
		      enum dpmac_counter type,
		      u64 *counter)
{
	struct dpmac_cmd_get_counter *dpmac_cmd;
	struct dpmac_rsp_get_counter *dpmac_rsp;
	struct fsl_mc_command cmd = { 0 };
	int err = 0;

	/* prepare command */
	cmd.header = mc_encode_cmd_header(DPMAC_CMDID_GET_COUNTER,
					  cmd_flags,
					  token);
	dpmac_cmd = (struct dpmac_cmd_get_counter *)cmd.params;
	dpmac_cmd->type = type;

	/* send command to mc */
	err = mc_send_command(mc_io, &cmd);
	if (err)
		return err;

	/* retrieve response parameters */
	dpmac_rsp = (struct dpmac_rsp_get_counter *)cmd.params;
	*counter = le64_to_cpu(dpmac_rsp->counter);

	return 0;
}

int dpmac_set_irq_enable(struct fsl_mc_io *mc_io,
			 u32 cmd_flags,
			 u16 token,
			 u8 irq_index,
			 u8 en)
{
	struct dpmac_cmd_set_irq_enable *dpmac_cmd;
	struct fsl_mc_command cmd = { 0 };

	/* prepare command */
	cmd.header = mc_encode_cmd_header(DPMAC_CMDID_SET_IRQ_ENABLE,
			cmd_flags,
			token);
	dpmac_cmd = (struct dpmac_cmd_set_irq_enable *)cmd.params;
	dpmac_cmd->irq_index = irq_index;
	dpmac_cmd->enable = en;

	/* send command to mc*/
	return mc_send_command(mc_io, &cmd);
}

int dpmac_get_irq_enable(struct fsl_mc_io *mc_io,
			 u32 cmd_flags,
			 u16 token,
			 u8 irq_index,
			 u8 *en)
{
	struct dpmac_cmd_get_irq_enable *dpmac_cmd;
	struct dpmac_rsp_get_irq_enable *dpmac_rsp;
	struct fsl_mc_command cmd = { 0 };
	int err;

	/* prepare command */
	cmd.header = mc_encode_cmd_header(DPMAC_CMDID_GET_IRQ_ENABLE,
			cmd_flags,
			token);
	dpmac_cmd = (struct dpmac_cmd_get_irq_enable *)cmd.params;
	dpmac_cmd->irq_index = irq_index;

	/* send command to mc*/
	err = mc_send_command(mc_io, &cmd);
	if (err)
		return err;

	/* retrieve response parameters */
	dpmac_rsp = (struct dpmac_rsp_get_irq_enable *)cmd.params;
	*en = dpmac_rsp->enabled;

	return 0;
}
